﻿//--------------------------------------------------------------------------------------------------------------------------------------------------------
// <copyright file="SigningIn.cs">
//     Copyright © 2010 Oli Francis
//     This source code is subject to the terms and conditions of the MIT license. A copy of the license can be found in the license.txt 
//     file at the root of this distribution. By using this source code in any fashion, you are agreeing to be bound by the terms of the MIT License. 
//     You must not remove this notice from the software.
// </copyright>
// <author>Oli</author>
//-------------------------------------------------------------------------------------------------------------------------------------------------------

namespace TeamBrain.Tests.Units.Security
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using Moq;

    using NUnit.Framework;

    using TeamBrain.Domain.Model;
    using TeamBrain.Domain.Services.Exceptions;
    using TeamBrain.Domain.Services.Security;

    [TestFixture]
    public class SigningIn : SecurityAction
    {
        [Test]
        public void ShouldFailIfEmailDoesntExist()
        {
            // act
            var result = this.SecurityService.AttemptSignIn("oli@notthere.com", "password", true);

            // assert
            Assert.AreEqual(SignInOutcome.CredentialsIncorrect, result);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldFailIfPasswordIsWrong()
        {
            // arrange
            var user = new User() { Salt = "Salty", PasswordHash = "StoredHash" };
            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("DifferentHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.CredentialsIncorrect, result);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldIncrementFailedAttemptCountIfDetailsWrong()
        {
            // arrange
            var user = new User { Salt = "Salty", PasswordHash = "StoredHash" };
            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("DifferentHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.CredentialsIncorrect, result);
            Assert.AreEqual(1, user.FailedSignInAttemptCount);
            Assert.IsNotNull(user.LastFailedSignInAttempt);
            this.UnitOfWork.Verify(u => u.Flush());
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldReturnLockedOutcomeIfWrongAfterFourTries()
        {
            // arrange
            var time = DateTime.Now.Subtract(TimeSpan.FromSeconds(5));
            var user = new User
                {
                    Salt = "Salty", 
                    PasswordHash = "StoredHash", 
                    FailedSignInAttemptCount = 4, 
                    LastFailedSignInAttempt = time
                };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("DifferentHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.AccountLocked, result);
            Assert.AreEqual(5, user.FailedSignInAttemptCount);
            Assert.AreNotEqual(time, user.LastFailedSignInAttempt);
            this.UnitOfWork.Verify(u => u.Flush());
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldReturnLockedOutcomeIfDetailsCorrectButAccountLocked()
        {
            // arrange
            var lastFailed = DateTime.Now.Subtract(TimeSpan.FromSeconds(5));
            var user = new User
            {
                Salt = "Salty",
                PasswordHash = "StoredHash",
                FailedSignInAttemptCount = 5,
                LastFailedSignInAttempt = lastFailed
            };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("StoredHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.AccountLocked, result);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldReturnDisabledOutcomeIfDetailsCorrectButAccountDisabled()
        {
            // arrange
            var user = new User
            {
                Salt = "Salty",
                PasswordHash = "StoredHash",
                Disabled = true
            };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("StoredHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.AccountDisabled, result);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldResetFailedCountIfLockoutPeriodHasExpired()
        {
            // arrange
            var lastFailed = DateTime.Now.Subtract(TimeSpan.FromMinutes(20));
            var user = new User
                {
                    Salt = "Salty",
                    PasswordHash = "StoredHash",
                    FailedSignInAttemptCount = 8,
                    LastFailedSignInAttempt = lastFailed
                };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("DifferentHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.CredentialsIncorrect, result);
            Assert.AreEqual(1, user.FailedSignInAttemptCount);
            Assert.AreNotEqual(lastFailed, user.LastFailedSignInAttempt);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
        }

        [Test]
        public void ShouldSignInUserIfDetailsCorrect()
        {
            // arrange
            var user = new User
            {
                Salt = "Salty",
                PasswordHash = "StoredHash",
            };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("StoredHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", false);

            // assert
            Assert.AreEqual(SignInOutcome.Success, result);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), false));
        }

        [Test]
        public void ShouldCreatePersistedCookieIfUserTicksRememberMe()
        {
            // arrange
            var user = new User
            {
                Salt = "Salty",
                PasswordHash = "StoredHash",
            };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("StoredHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(SignInOutcome.Success, result);
            this.AuthenticationProvider.Verify(a => a.SetAuthCookie(It.IsAny<int>(), true));
        }

        [Test]
        public void ShouldResetFailedAttemptCountForSuccessfulAttempt()
        {
            // arrange
            var lastFailed = DateTime.Now.Subtract(TimeSpan.FromMinutes(2));
            var user = new User
            {
                Salt = "Salty",
                PasswordHash = "StoredHash",
                FailedSignInAttemptCount = 3,
                LastFailedSignInAttempt = lastFailed
            };

            this.UserRepository.Setup(r => r.GetByEmailAddress(It.IsAny<string>())).Returns(user);
            this.PasswordHasher.Setup(h => h.GetPasswordHash("Salty", "AttemptedPassword")).Returns("StoredHash");

            // act
            var result = this.SecurityService.AttemptSignIn("oli@test.com", "AttemptedPassword", true);

            // assert
            Assert.AreEqual(0, user.FailedSignInAttemptCount);
            Assert.IsNull(user.LastFailedSignInAttempt);
            this.UnitOfWork.Verify(u => u.Flush());
        }
    }
}
